#!/usr/bin/env python  
#-*- coding:utf-8 _*-  
""" 
@author:hello_life 
@license: Apache Licence 
@file: metric_utils_2.py 
@time: 2022/05/09
@software: PyCharm 
description:
"""
import logging
from seqeval.metrics import f1_score,classification_report

from utils.data_utils import id_to_label


#############################################################
def get_f1_score(y_true,y_pred,label_lens,config):
    """
    通过seqeval计算f1_score
    :param y_true: [batch,seq_len]
    :param y_pred: [batch,seq_len]
    :return:
    """
    y_true_tag,y_pred_tag=[],[]
    for i,lens in enumerate(label_lens):
        #去除seq,cls的标签
        if lens<len(y_true[i])-1:  #说明做了padding，进一步看,padding后标签长度-原始句子长度>1,说明所有标签都可以取到
            y_true_tag.append(id_to_label(y_true[i][1:lens+1], config))
            y_pred_tag.append(id_to_label(y_pred[i][1:lens+1], config))
        else: #padding后标签长度-原始句子长度=1;说明最后一个标签取不到，比如lens=9,y_true_len=10,或做了truncation
            y_true_tag.append(id_to_label(y_true[i][1:-1], config))
            y_pred_tag.append(id_to_label(y_pred[i][1:-1], config))
    f1=f1_score(y_true_tag,y_pred_tag)
    return f1


def get_f1_score_crf(y_true,y_pred,label_lens,config):
    """

    :param y_true: [batch,seq_len] 设定的句子长度
    :param y_pred: [batch,true_seq_len] 真实的句子长度
    :param label_lens:
    :param config:
    :return:
    """
    y_true_tag,y_pred_tag=[],[]
    for i,lens in enumerate(label_lens):
        #去除seq,cls的标签,和padding标签
        if lens<len(y_true[i])-1:  #说明做了padding，进一步看,padding后标签长度-原始句子长度>1,说明所有标签都可以取到
            y_true_tag.append(id_to_label(y_true[i][1:lens+1], config))
        else: #padding后标签长度-原始句子长度=1;说明最后一个标签取不到，比如lens=9,y_true_len=10,或做了truncation
            y_true_tag.append(id_to_label(y_true[i][1:-1], config))
        y_pred_tag.append(id_to_label(y_pred[i],config))

    f1=classification_report(y_true_tag,y_pred_tag)

    return f1


#############################################################
def get_entities(seq,config):
    """
    从label_list中获得实体，
    :param label_list:
    :return:list: list of (chunk_type, chunk_start, chunk_end).
    :Example:
        seq = [['1', '2', '0', '3']]
        get_entities(seq)
        [('PER', 0, 1), ('LOC', 3, 3)]
    """

    if any(isinstance(s, list) for s in seq):
        seq = [item for sublist in seq for item in sublist + [config.label2id['O']]]

    #id转化为label
    seq=id_to_label(seq,config)

    prev_tag="O"
    prev_type=""
    begin_offset=0
    chunks=[]

    for i,chunk in enumerate(seq+["O"]):
        tag=chunk[0]
        type_=chunk.split("-")[-1]

        if end_of_chunk(prev_tag,tag,prev_type,type_):
            chunks.append((prev_type,begin_offset,i-1))

        if start_of_chunk(prev_tag,tag,prev_type,type_):
            begin_offset=i
        prev_type=type_
        prev_tag=tag
    return chunks

def end_of_chunk(prev_tag,tag,prev_type,type):
    """
Checks if a chunk ended between the previous and current word.

    Args:
        prev_tag: previous chunk tag.
        tag: current chunk tag.
        prev_type: previous type.
        type_: current type.

    Returns:
        chunk_end: boolean.
    """
    chunk_end=False

    if prev_tag=="S":
        chunk_end=True
    # pred_label中可能出现这种情形
    if prev_tag == "B" and (tag == "B" or tag == "O" or tag == "S"):
        chunk_end=True

    if prev_tag == "I" and (tag == "B" or tag == "S" or tag == "O"):
        chunk_end=True

    if prev_tag != "O" and prev_tag !="." and prev_type != type:
        chunk_end=True

    return chunk_end



def start_of_chunk(prev_tag, tag, prev_type, type_):
    """Checks if a chunk started between the previous and current word.

    Args:
        prev_tag: previous chunk tag.
        tag: current chunk tag.
        prev_type: previous type.
        type_: current type.

    Returns:
        chunk_start: boolean.
    """
    chunk_start = False

    if tag == "B" or tag == "S":
        chunk_start=True

    if prev_tag=="S" and tag =="I":
        chunk_start=True

    if prev_tag=="O" and tag == "I":
        chunk_start=True


    if tag != "O" and tag != "." and prev_type!=type_:
        chunk_start=True

    return chunk_start


#############################################################
def get_f1(y_pred,y_label,config):
    """
    得到实体，计算f1值,
    :param y_pred:  [['B-PER', 'I-PER', 'O', 'B-GAME']]
    :param y_true:  [['B-PER', 'I-PER', 'O', 'B-LOC']]
    :return:
    """

    pred_entity=set(get_entities(y_pred,config))
    label_entity=set(get_entities(y_label,config))
    true_num=len(pred_entity&label_entity)
    pred_num=len(pred_entity)
    label_num=len(label_entity)

    p=true_num/pred_num if pred_num>0 else 0
    r=true_num/label_num if label_num>0 else 0
    f1_score_total=2*p*r/(p+r) if p+r >0 else 0

    if config.mode=="dev":
        return f1_score_total

    else:
        f1_score_class={}

        for label in config.label:
            true_entities_label=set()
            pred_entities_label=set()
            for t in label_entity:
                if t[0]==label:
                    true_entities_label.add(t)
            for p in pred_entity:
                if p[0]==label:
                    pred_entities_label.add(p)
            true_num = len(true_entities_label & pred_entities_label)
            pred_num = len(pred_entities_label)
            label_num = len(true_entities_label)

            p_label = true_num / pred_num if pred_num > 0 else 0
            r_label = true_num / label_num if label_num > 0 else 0
            f1_label = 2 * p_label * r_label / (p_label + r_label) if p_label + r_label > 0 else 0
            f1_score_class[label]=f1_label
        return f1_score_total,f1_score_class

#############################################################